// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_H_
#define CONTENT_BROWSER_APPCACHE_APPCACHE_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "content/browser/appcache/appcache_database.h"
#include "content/browser/appcache/appcache_entry.h"
#include "content/browser/appcache/appcache_manifest_parser.h"
#include "content/common/content_export.h"
#include "url/gurl.h"

namespace net {
class IOBuffer;
}

namespace content {
FORWARD_DECLARE_TEST(AppCacheTest, InitializeWithManifest);
FORWARD_DECLARE_TEST(AppCacheTest, ToFromDatabaseRecords);
class AppCacheExecutableHandler;
class AppCacheGroup;
class AppCacheHost;
class AppCacheStorage;
class AppCacheTest;
class AppCacheStorageImplTest;
class AppCacheUpdateJobTest;

// Set of cached resources for an application. A cache exists as long as a
// host is associated with it, the cache is in an appcache group or the
// cache is being created during an appcache upate.
class CONTENT_EXPORT AppCache
    : public base::RefCounted<AppCache> {
public:
    typedef std::map<GURL, AppCacheEntry> EntryMap;
    typedef std::set<AppCacheHost*> AppCacheHosts;

    AppCache(AppCacheStorage* storage, int64_t cache_id);

    int64_t cache_id() const { return cache_id_; }

    AppCacheGroup* owning_group() const { return owning_group_.get(); }

    bool is_complete() const { return is_complete_; }
    void set_complete(bool value) { is_complete_ = value; }

    // Adds a new entry. Entry must not already be in cache.
    void AddEntry(const GURL& url, const AppCacheEntry& entry);

    // Adds a new entry or modifies an existing entry by merging the types
    // of the new entry with the existing entry. Returns true if a new entry
    // is added, false if the flags are merged into an existing entry.
    bool AddOrModifyEntry(const GURL& url, const AppCacheEntry& entry);

    // Removes an entry from the EntryMap, the URL must be in the set.
    void RemoveEntry(const GURL& url);

    // Do not store or delete the returned ptr, they're owned by 'this'.
    AppCacheEntry* GetEntry(const GURL& url);
    const AppCacheEntry* GetEntryWithResponseId(int64_t response_id)
    {
        return GetEntryAndUrlWithResponseId(response_id, nullptr);
    }
    const AppCacheEntry* GetEntryAndUrlWithResponseId(int64_t response_id,
        GURL* optional_url);
    const EntryMap& entries() const { return entries_; }

    // The AppCache owns the collection of executable handlers that have
    // been started for this instance. The getter looks up an existing
    // handler returning null if not found, the GetOrCreate method will
    // cons one up if not found.
    // Do not store the returned ptrs, they're owned by 'this'.
    AppCacheExecutableHandler* GetExecutableHandler(int64_t response_id);
    AppCacheExecutableHandler* GetOrCreateExecutableHandler(
        int64_t response_id,
        net::IOBuffer* handler_source);

    // Returns the URL of the resource used as entry for 'namespace_url'.
    GURL GetFallbackEntryUrl(const GURL& namespace_url) const
    {
        return GetNamespaceEntryUrl(fallback_namespaces_, namespace_url);
    }
    GURL GetInterceptEntryUrl(const GURL& namespace_url) const
    {
        return GetNamespaceEntryUrl(intercept_namespaces_, namespace_url);
    }

    AppCacheHosts& associated_hosts() { return associated_hosts_; }

    bool IsNewerThan(AppCache* cache) const
    {
        // TODO(michaeln): revisit, the system clock can be set
        // back in time which would confuse this logic.
        if (update_time_ > cache->update_time_)
            return true;

        // Tie breaker. Newer caches have a larger cache ID.
        if (update_time_ == cache->update_time_)
            return cache_id_ > cache->cache_id_;

        return false;
    }

    base::Time update_time() const { return update_time_; }

    int64_t cache_size() const { return cache_size_; }

    void set_update_time(base::Time ticks) { update_time_ = ticks; }

    // Initializes the cache with information in the manifest.
    // Do not use the manifest after this call.
    void InitializeWithManifest(AppCacheManifest* manifest);

    // Initializes the cache with the information in the database records.
    void InitializeWithDatabaseRecords(
        const AppCacheDatabase::CacheRecord& cache_record,
        const std::vector<AppCacheDatabase::EntryRecord>& entries,
        const std::vector<AppCacheDatabase::NamespaceRecord>& intercepts,
        const std::vector<AppCacheDatabase::NamespaceRecord>& fallbacks,
        const std::vector<AppCacheDatabase::OnlineWhiteListRecord>& whitelists);

    // Returns the database records to be stored in the AppCacheDatabase
    // to represent this cache.
    void ToDatabaseRecords(
        const AppCacheGroup* group,
        AppCacheDatabase::CacheRecord* cache_record,
        std::vector<AppCacheDatabase::EntryRecord>* entries,
        std::vector<AppCacheDatabase::NamespaceRecord>* intercepts,
        std::vector<AppCacheDatabase::NamespaceRecord>* fallbacks,
        std::vector<AppCacheDatabase::OnlineWhiteListRecord>* whitelists);

    bool FindResponseForRequest(const GURL& url,
        AppCacheEntry* found_entry, GURL* found_intercept_namespace,
        AppCacheEntry* found_fallback_entry, GURL* found_fallback_namespace,
        bool* found_network_namespace);

    // Populates the 'infos' vector with an element per entry in the appcache.
    void ToResourceInfoVector(AppCacheResourceInfoVector* infos) const;

    static const AppCacheNamespace* FindNamespace(
        const AppCacheNamespaceVector& namespaces,
        const GURL& url);

private:
    friend class AppCacheGroup;
    friend class AppCacheHost;
    friend class content::AppCacheTest;
    friend class content::AppCacheStorageImplTest;
    friend class content::AppCacheUpdateJobTest;
    friend class base::RefCounted<AppCache>;

    ~AppCache();

    // Use AppCacheGroup::Add/RemoveCache() to manipulate owning group.
    void set_owning_group(AppCacheGroup* group) { owning_group_ = group; }

    // FindResponseForRequest helpers
    const AppCacheNamespace* FindInterceptNamespace(const GURL& url)
    {
        return FindNamespace(intercept_namespaces_, url);
    }
    const AppCacheNamespace* FindFallbackNamespace(const GURL& url)
    {
        return FindNamespace(fallback_namespaces_, url);
    }
    bool IsInNetworkNamespace(const GURL& url)
    {
        return FindNamespace(online_whitelist_namespaces_, url) != nullptr;
    }

    GURL GetNamespaceEntryUrl(const AppCacheNamespaceVector& namespaces,
        const GURL& namespace_url) const;

    // Use AppCacheHost::Associate*Cache() to manipulate host association.
    void AssociateHost(AppCacheHost* host)
    {
        associated_hosts_.insert(host);
    }
    void UnassociateHost(AppCacheHost* host);

    const int64_t cache_id_;
    scoped_refptr<AppCacheGroup> owning_group_;
    AppCacheHosts associated_hosts_;

    EntryMap entries_; // contains entries of all types

    AppCacheNamespaceVector intercept_namespaces_;
    AppCacheNamespaceVector fallback_namespaces_;
    AppCacheNamespaceVector online_whitelist_namespaces_;
    bool online_whitelist_all_;

    bool is_complete_;

    // when this cache was last updated
    base::Time update_time_;

    int64_t cache_size_;

    typedef std::map<int64_t, std::unique_ptr<AppCacheExecutableHandler>>
        HandlerMap;
    HandlerMap executable_handlers_;

    // to notify storage when cache is deleted
    AppCacheStorage* storage_;

    FRIEND_TEST_ALL_PREFIXES(content::AppCacheTest, InitializeWithManifest);
    FRIEND_TEST_ALL_PREFIXES(content::AppCacheTest, ToFromDatabaseRecords);
    DISALLOW_COPY_AND_ASSIGN(AppCache);
};

} // namespace content

#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_H_
